package com.anden.panningview;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.element.Element;
import ohos.agp.render.Canvas;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;

/**
 * View can display a {@link } with a {@link Panning} animation
 */
public class PanningView extends Component implements Component.DrawTask {
    static final String TAG = "PanningView";
    private static final String DURATION = "duration";
    private static final String DRAWABLE = "drawable";
    private static final String AUTO_START = "autoStart";

    /**
     * PanningListener
     */
    public interface PanningListener {
        void onPanningStart(PanningView panningView);

        void onPanningEnd(PanningView panningView);
    }

    private static final long TRANSITION_DURATION = 3000;

    private static final int PAUSE_STATE = 0;
    private static final int START_STATE = 1;
    private int state = PAUSE_STATE;
    private Matrix matrix = new Matrix();
    private RectFloat displayRect = new RectFloat();
    private long duration = TRANSITION_DURATION;
    private boolean autoStart = false;
    private long lastFrameTime;
    private long elapsedTime;
    private long pausedTime;
    private float scaleFactor;
    private Element drawable = getBackgroundElement();
    private Panning PANNING = new HorizontalPanning(HorizontalPanning.LEFT_TO_RIGHT);
    private Panning panning = PANNING;
    private PanningListener panningListener;

    public PanningView(Context context) {
        this(context, null);
    }

    public PanningView(Context context, AttrSet attrs) {
        this(context, attrs, 0);
    }

    public PanningView(Context context, AttrSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        if (attrs != null) {
            duration = attrs.getAttr(DURATION).isPresent() ?
                    attrs.getAttr(DURATION).get().getIntegerValue() : TRANSITION_DURATION;
            autoStart = attrs.getAttr(AUTO_START).isPresent() && attrs.getAttr(AUTO_START).get().getBoolValue();
            drawable = attrs.getAttr(DRAWABLE).isPresent() ?
                    attrs.getAttr(DRAWABLE).get().getElement() : drawable;
            LogUtil.error("Data drawable", "=" + drawable);
        }
        init();
    }

    private void init() {
        onAttachedToWindow();
        addDrawTask(this);
        setDrawable(drawable);
    }

    /**
     * PanningListener
     *
     * @param widthMeasureSpec  int
     * @param heightMeasureSpec int
     */
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int desiredWidth = 0;
        int desiredHeight = 0;
        if (drawable != null) {
            desiredWidth = drawable.getWidth();
            desiredHeight = drawable.getHeight();
        }
        int widthMode = EstimateSpec.getMode(widthMeasureSpec);
        int widthSize = EstimateSpec.getSize(widthMeasureSpec);
        int heightMode = EstimateSpec.getMode(heightMeasureSpec);
        int heightSize = EstimateSpec.getSize(heightMeasureSpec);
        int width;
        int height;
        if (widthMode == EstimateSpec.PRECISE) {
            width = widthSize;
        } else if (widthMode == EstimateSpec.NOT_EXCEED) {
            width = Math.min(desiredWidth, widthSize);
        } else {
            width = desiredWidth;
        }
        if (heightMode == EstimateSpec.PRECISE) {
            height = heightSize;
        } else if (heightMode == EstimateSpec.NOT_EXCEED) {
            height = Math.min(desiredHeight, heightSize);
        } else {
            height = desiredHeight;
        }
        setComponentSize(width, height);
        calculateValues();
    }

    /**
     * onAttachedToWindow
     */
    protected void onAttachedToWindow() {
        if (autoStart) {
            start();
        }
    }

    private void calculateValues() {
        if (drawable == null) {
            return;
        }
        scaleFactor = (float) getHeight() / (float) drawable.getHeight();
        drawable.setBounds(0, 0, drawable.getWidth(), drawable.getHeight());
        matrix.reset();
        matrix.postScale(scaleFactor, scaleFactor);
        refreshDisplayRect();
    }

    private void refreshDisplayRect() {
        if (drawable == null) {
            return;
        }
        displayRect.setPivot(drawable.getWidth(), drawable.getHeight());
        matrix.mapRect(displayRect);
        panning.setSize(displayRect, new RectFloat(0, 0,
                getWidth(), getHeight()));
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        onMeasure(getWidth(), getHeight());
        if (drawable == null) {
            return;
        }
        if (state == START_STATE) {
            elapsedTime = elapsedTime + System.currentTimeMillis() - lastFrameTime;
            float fval = Math.max(0, Math.min(1, elapsedTime / (float) duration));
            matrix.reset();
            matrix.postScale(scaleFactor, scaleFactor);
            canvas.concat(matrix);
            matrix.postTranslate(panning.getX(fval), panning.getY(fval));
            drawable.drawToCanvas(canvas);
        }
        if (state == PAUSE_STATE) {
            return;
        }

        if (elapsedTime < duration) {
            lastFrameTime = System.currentTimeMillis();
            invalidate();
        } else {
            if (panningListener != null) {
                panningListener.onPanningEnd(this);
            }
        }
    }

    /**
     * Sets the {@link Panning} implementation of the animation
     *
     * @param panning Panning
     */
    public void setPanning(Panning panning) {
        this.panning = panning;
        postLayout();
    }

    private void setDrawable(Element drawable) {
        this.drawable = drawable;
    }

    /**
     * Start the animation
     */
    public void start() {
        state = START_STATE;
        lastFrameTime = System.currentTimeMillis();
        LogUtil.error("Data Start", "=" + lastFrameTime);
        elapsedTime = 0;
        invalidate();
        if (panningListener != null) {
            panningListener.onPanningStart(this);
        }
    }

    /**
     * Resume the current animation
     */
    public void resume() {
        state = START_STATE;
        lastFrameTime += System.currentTimeMillis() - pausedTime;
        invalidate();
    }

    /**
     * Pause the current animation
     */
    public void pause() {
        state = PAUSE_STATE;
        pausedTime = System.currentTimeMillis();
        invalidate();
    }

    /**
     * setPanningListener
     *
     * @param panningListener Panning
     */
    public void setPanningListener(PanningListener panningListener) {
        this.panningListener = panningListener;
    }
}